﻿//---------------------------------------------------------------------
// <copyright file="EntityDescriptorValueCalculator.cs" company="Microsoft">
//      Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
// </copyright>
//---------------------------------------------------------------------

namespace Microsoft.Test.Taupo.Astoria.Client
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.OData.Client;
    using Microsoft.Test.Taupo.Astoria.Common;
    using Microsoft.Test.Taupo.Astoria.Contracts;
    using Microsoft.Test.Taupo.Astoria.Contracts.Client;
    using Microsoft.Test.Taupo.Astoria.Contracts.OData;
    using Microsoft.Test.Taupo.Common;
    using Microsoft.Test.Taupo.Contracts;

    /// <summary>
    /// Contract for calculating the expected entity descriptor values that will be generated by the data service context
    /// </summary>
    [ImplementationName(typeof(IEntityDescriptorValueCalculator), "Default")]
    public class EntityDescriptorValueCalculator : IEntityDescriptorValueCalculator
    {
        /// <summary>
        /// Gets or sets the literal converter to use
        /// </summary>
        [InjectDependency(IsRequired = true)]
        public IODataLiteralConverter LiteralConverter { get; set; }

        /// <summary>
        /// Calculates an entity id based on the base uri, entity set resolver, set name, and entity key values
        /// </summary>
        /// <param name="contextData">The context data</param>
        /// <param name="entitySetName">the entity set name</param>
        /// <param name="entity">The entity to generate an id for</param>
        /// <returns>The id for the given entity</returns>
        public Uri CalculateEntityId(DataServiceContextData contextData, string entitySetName, object entity)
        {
            ExceptionUtilities.CheckArgumentNotNull(contextData, "contextData");
            ExceptionUtilities.CheckStringArgumentIsNotNullOrEmpty(entitySetName, "entitySetName");
            ExceptionUtilities.CheckArgumentNotNull(entity, "entity");

            if (contextData.BaseUri != null)
            {
                ExceptionUtilities.Assert(contextData.BaseUri.IsAbsoluteUri, "Base uri must be absolute. Uri was: '{0}'", contextData.BaseUri.OriginalString);
                return new Uri(UriHelpers.ConcatenateUriSegments(contextData.BaseUri.AbsoluteUri, Uri.EscapeDataString(entitySetName) + this.CalculateEntityKey(entity)));
            }
            else
            {
                ExceptionUtilities.CheckObjectNotNull(contextData.ResolveEntitySet, "Entity set resolver cannot be null if base uri is null");
                var resolvedUri = contextData.ResolveEntitySet(entitySetName);
                ExceptionUtilities.CheckObjectNotNull(resolvedUri, "Entity set resolver returned null for set name '{0}'", entitySetName);
                return new Uri(resolvedUri.AbsoluteUri + this.CalculateEntityKey(entity));
            }   
        }

        /// <summary>
        /// Calculates an edit link for based on the base uri, entity set resolver, set name, and entity key values
        /// </summary>
        /// <param name="contextData">The context data</param>
        /// <param name="entitySetName">the entity set name</param>
        /// <param name="entity">The entity to generate an edit link for</param>
        /// <returns>The edit link for the given entity</returns>
        public Uri CalculateEditLink(DataServiceContextData contextData, string entitySetName, object entity)
        {
            return this.CalculateEntityId(contextData, entitySetName, entity);
        }

        /// <summary>
        /// Calculates the key for an entity, which is used to build uris for link update and delete operations
        /// </summary>
        /// <param name="entity">The entity to build a key for</param>
        /// <returns>The key of the entity</returns>
        public string CalculateEntityKey(object entity)
        {
            var entityType = entity.GetType();
            var keyProperties = entityType.GetCustomAttributes(typeof(KeyAttribute), true)
                .Cast<KeyAttribute>().SelectMany(att => att.KeyNames)
                .Concat(entityType.GetProperties().Select(p => p.Name).Where(p => p.EndsWith("ID", StringComparison.Ordinal)))
                .Distinct()
                .OrderBy(p => p, Comparer<string>.Default)
                .Select(p => entityType.GetProperty(p))
                .ToList();

            ExceptionUtilities.CheckCollectionNotEmpty(keyProperties, "keyProperties");
            ExceptionUtilities.CheckCollectionDoesNotContainNulls(keyProperties, "keyProperties");

            StringBuilder builder = new StringBuilder();
            this.LiteralConverter.AppendKeyExpression(builder, keyProperties.Select(p => new NamedValue(p.Name, p.GetValue(entity, null))));
            return builder.ToString();
        }
    }
}
